package me.spring.cloud.common.components.util.math;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Random;
import me.spring.cloud.common.components.util.lang.StringUtil;

public class BigDecimalUtil {

  // 默认除法运算精度
  private static final int DEF_DIV_SCALE = 10;

  public static final BigDecimal zoom = new BigDecimal(10000);

  public static final BigDecimal hundred = new BigDecimal(100);

  public static final BigDecimal nehundred = new BigDecimal(-100);

  /**
   * 提供精确的加法运算。
   * @param v1 被加数
   * @param v2 加数
   * @Return 两个参数的和
   */
  public static double add(double v1, double v2) {
    BigDecimal b1 = new BigDecimal(Double.toString(v1));
    BigDecimal b2 = new BigDecimal(Double.toString(v2));
    return b1.add(b2).doubleValue();
  }

  /**
   * 提供精确的减法运算。
   * @param v1 被减数
   * @param v2 减数
   * @Return 两个参数的差
   */
  public static double subtract(double v1, double v2) {
    BigDecimal b1 = new BigDecimal(Double.toString(v1));
    BigDecimal b2 = new BigDecimal(Double.toString(v2));
    return b1.subtract(b2).doubleValue();
  }

  /**
   * 提供精确的乘法运算。
   * @param v1 被乘数
   * @param v2 乘数
   * @Return 两个参数的积
   */
  public static double multiply(double v1, double v2) {
    BigDecimal b1 = new BigDecimal(Double.toString(v1));
    BigDecimal b2 = new BigDecimal(Double.toString(v2));
    return b1.multiply(b2).doubleValue();
  }

  /**
   * 提供（相对）精确的除法运算，当发生除不尽的情况时，精确到小数点以后10位，以后的数字四舍五入。
   * @param v1 被除数
   * @param v2 除数
   * @Return 两个参数的商
   */
  public static double divide(double v1, double v2) {
    return divide(v1, v2, DEF_DIV_SCALE);
  }

  /**
   * 提供（相对）精确的除法运算。当发生除不尽的情况时，由scale参数指定精度，以后的数字四舍五入。
   * @param v1 被除数
   * @param v2 除数
   * @param scale 表示表示需要精确到小数点以后几位。
   * @Return 两个参数的商
   */
  public static double divide(double v1, double v2, int scale) {
    if (scale < 0) {
      throw new IllegalArgumentException("The scale must be a positive integer or zero");
    }
    BigDecimal b = new BigDecimal(Double.toString(v1));
    BigDecimal one = new BigDecimal(v2);
    return b.divide(one, scale, BigDecimal.ROUND_HALF_UP).doubleValue();
  }

  private static boolean isEmpty(Object object) {
    return null == object;
  }

  public static BigDecimal add(BigDecimal b1, BigDecimal b2) {
    if (!isEmpty(b1) && !isEmpty(b2)) {
      return b1.add(b2);
    }
    return isEmpty(b1) ? (isEmpty(b2) ? null : b2) : b1;
  }

  public static BigDecimal subtract(BigDecimal b1, BigDecimal b2) {
    if (!isEmpty(b1) && !isEmpty(b2)) {
      return b1.subtract(b2);
    }
    return isEmpty(b1) ? (isEmpty(b2) ? null : b2) : b1;
  }

  public static BigDecimal multiply(BigDecimal b1, BigDecimal b2) {
    if (!isEmpty(b1) && !isEmpty(b2)) {
      return b1.multiply(b2);
    }
    return null;
  }

  public static BigDecimal multiply(String b1, BigDecimal b2) {
    if (!StringUtil.isEmpty(b1) && !isEmpty(b2)) {
      return new BigDecimal(b1).multiply(b2);
    }
    return null;
  }

  public static BigDecimal divide(String s1, BigDecimal b2, int scale, RoundingMode roundingMode) {
    if (!StringUtil.isEmpty(s1) && !isEmpty(b2) && !isEqThanZero(b2)) {
      return new BigDecimal(s1).divide(b2, scale, roundingMode);
    }
    return null;
  }

  public static BigDecimal divide(BigDecimal b1, BigDecimal b2, int scale, RoundingMode roundingMode) {
    if (!isEmpty(b1) && !isEmpty(b2) && !isEqThanZero(b2)) {
      return b1.divide(b2, scale, roundingMode);
    }
    return null;
  }

  public static int getRandom(int min, int max) {
    return new Random().nextInt(max) % (max - min + 1) + min;
  }

  public static long getRandomLong(long begin, long end) {
    return begin + (long) (Math.random() * (end - begin));
  }

  public static long getRemainder(long molecula, int denominator) {
    if (denominator == 0) {
      return 0L;
    }
    return molecula % denominator;
  }

  public static long getFloor(long molecula, int denominator) {
    if (denominator == 0) {
      return 0L;
    }
    return (long) Math.floor(molecula / denominator);
  }

  public static boolean isEqThanZero(BigDecimal amount) {
    if (null == amount) {
      return false;
    }
    if (amount.compareTo(BigDecimal.ZERO) == 0) {
      return true;
    }
    return false;
  }

  public static boolean isGreatThanZero(BigDecimal amount) {
    if (null == amount) {
      return false;
    }
    if (amount.compareTo(BigDecimal.ZERO) > 0) {
      return true;
    }
    return false;
  }

  public static boolean isLessThanZero(BigDecimal amount) {
    if (null == amount) {
      return false;
    }
    if (amount.compareTo(BigDecimal.ZERO) < 0) {
      return true;
    }
    return false;
  }

  public static BigDecimal formatBigDecimal(BigDecimal b, int scale, RoundingMode roundingMode) {
    if (isEmpty(b)) {
      return null;
    }
    return b.setScale(scale, roundingMode);
  }

  public static boolean isGreatThan(BigDecimal amount1, BigDecimal amount2) {
    return amount1.compareTo(amount2) > 0;
  }

  public static boolean isLessThan(BigDecimal amount1, BigDecimal amount2) {
    return amount1.compareTo(amount2) < 0;
  }

  public static boolean isEqualThan(BigDecimal amount1, BigDecimal amount2) {
    return amount1.compareTo(amount2) == 0;
  }

  public static void zoomOut(Object obj) {
    try {
      String[] arrV = (String[]) obj.getClass().getField("zoomArr").get(obj);
      for (String methodName : arrV) {
        methodName = methodName.substring(0, 1).toUpperCase() + methodName.substring(1);
        BigDecimal val = (BigDecimal) obj.getClass().getMethod("get" + methodName).invoke(obj);
        val = zoomOut(val);
        obj.getClass().getMethod("set" + methodName, BigDecimal.class).invoke(obj, val);
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

  public static BigDecimal zoom(BigDecimal amount) {
    if (null == amount) {
      return BigDecimal.ZERO;
    }
    return amount;
  }

  public static BigDecimal zoomZero(BigDecimal amount) {
    if (null == amount || isLessThanZero(amount)) {
      return BigDecimal.ZERO;
    }
    return amount;
  }

  public static BigDecimal zoomOut(BigDecimal amount) {
    return zoomOut(amount, zoom);
  }

  public static BigDecimal zoomOut(BigDecimal amount, BigDecimal zoom) {
    return zoomOut(amount, zoom, 0);
  }

  public static BigDecimal zoomOut(BigDecimal amount, BigDecimal zoom, int scale) {
    if (null == amount) {
      return amount;
    }
    return amount.multiply(zoom).setScale(scale);
  }

  public static BigDecimal compare2SameTime(BigDecimal b1, BigDecimal b2) {
    if (isEqThanZero(b1) && isEqThanZero(b2)) {
      return BigDecimal.ZERO;
    }
    if (isEqThanZero(b1) && !isEqThanZero(b2)) {
      return nehundred;
    }
    if (!isEqThanZero(b1) && isEqThanZero(b2)) {
      return hundred;
    }
    return divide(b1.subtract(b2), b2, 2, RoundingMode.HALF_UP).multiply(hundred);
  }

  /**
   * @return java.math.BigDecimal
   * @Author luohongquan
   * @Description 保留4位小数
   * @Date 14:58 2019/6/17
   * @Param [b1, b2]
   */
  public static BigDecimal compare2SameTime4Decimals(BigDecimal b1, BigDecimal b2) {
    if (isEqThanZero(b1) && isEqThanZero(b2)) {
      return BigDecimal.ZERO;
    }
    if (isEqThanZero(b1) && !isEqThanZero(b2)) {
      return nehundred;
    }
    if (!isEqThanZero(b1) && isEqThanZero(b2)) {
      return hundred;
    }
    return divide(b1.subtract(b2), b2, 4, RoundingMode.HALF_UP).multiply(hundred).setScale(2, RoundingMode.HALF_UP);
  }

  public static BigDecimal compare2SameTimeDevide(BigDecimal b1, BigDecimal b2) {
    return compare2SameTimeDevide(b1, b2, 2, RoundingMode.HALF_UP, true);
  }

  public static BigDecimal compare2SameTimeDevide(BigDecimal b1, BigDecimal b2, int scale, RoundingMode roundingMode,
      boolean isMultiplyHundred) {
    if (isEqThanZero(b1) && isEqThanZero(b2)) {
      return BigDecimal.ZERO;
    }
    if (isEqThanZero(b1) && !isEqThanZero(b2)) {
      return BigDecimal.ZERO;
    }
    if (!isEqThanZero(b1) && isEqThanZero(b2)) {
      return hundred;
    }
    if (isMultiplyHundred) {
      return divide(b1, b2, scale, roundingMode).multiply(hundred);
    } else {
      return divide(b1, b2, scale, roundingMode);
    }

  }

  public static BigDecimal compare2SameTimeDevide(BigDecimal b1, BigDecimal b2, int scale1, int scale2,
      RoundingMode roundingMode) {
    final BigDecimal decimal = compare2SameTimeDevide(b1, b2, scale1, roundingMode, true);
    return decimal.setScale(scale2, roundingMode);
  }

  public static BigDecimal compare2SameTimeDevideNoMultiply(BigDecimal b1, BigDecimal b2, int scale1, int scale2,
      RoundingMode roundingMode) {
    final BigDecimal decimal = compare2SameTimeDevide(b1, b2, scale1, roundingMode, false);
    return decimal.setScale(scale2, roundingMode);
  }

}